/*******************************************************************************
 * Copyright (c) 2008 Sylvain Sicard.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     Noel De Palma - initial API and implementation
 *     Sylvain Sicard - attributes to control nodes JVM debug parameters
 ******************************************************************************/
package fr.jade.service.nodes.factory;

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.NoSuchInterfaceException;
import org.objectweb.fractal.api.control.ContentController;
import org.objectweb.fractal.api.control.IllegalBindingException;
import org.objectweb.fractal.api.control.IllegalContentException;
import org.objectweb.fractal.api.control.IllegalLifeCycleException;
import org.objectweb.fractal.api.factory.GenericFactory;
import org.objectweb.fractal.api.factory.InstantiationException;
import org.objectweb.fractal.api.type.InterfaceType;
import org.objectweb.fractal.api.type.TypeFactory;
import org.objectweb.fractal.util.Fractal;

import fr.jade.base.JadeBaseComponent;
import fr.jade.fraclite.api.control.GenericAttributeController;
import fr.jade.fraclite.api.control.NoSuchAttributeException;
import fr.jade.fraclite.orb.registry.NamingService;
import fr.jade.reflex.api.control.ReflexController;
import fr.jade.reflex.util.Reflex;
import fr.jade.util.FractalUtil;
import fr.jade.util.JadeException;
import fr.jade.util.ShellCommand;



public class NodeFactoryImpl extends JadeBaseComponent implements NodeFactory {
	
  // Attributes
  protected static final String debugNodeAttName = "debugNode";
  protected boolean debugNode = false;
  
  protected static final String debugNodePortAttName = "debugNodePort";
  protected String debugNodePort = "8787";
  
  protected static final String userAttName = "user";
  protected String user;
  
  protected static final String[] listAtt = {userAttName, debugNodeAttName, debugNodePortAttName};
  
	// Bindings
	protected static final String allocatorContentItfName = "allocator-content";
	protected ContentController allocatorContent = null;

	protected static final String clustersContentItfName = "clusters-content";
	protected ContentController clustersContent = null;

	protected static final String registryItfName = "registry";
	protected NamingService registry = null;
	
	protected static final String registryConfigItfName = "registryconfig";
	protected GenericAttributeController registryconfig = null;
	
	protected static final String[] listItf = new String[] {allocatorContentItfName, clustersContentItfName, 
	                                                     registryConfigItfName, registryItfName};
	
	// BindingController
	@Override
	public String[] listFc () {
		return super.concat(listItf, super.listFc());
	}
	
	@Override
	public Object lookupFc (final String cItf) throws NoSuchInterfaceException {
		if (cItf.equals(allocatorContentItfName)) 
		return allocatorContent;
	 else if (cItf.equals(clustersContentItfName)) 
		return clustersContent;
	  else if (cItf.equals(registryItfName)) 
		return registry;
	 else if (cItf.equals(registryConfigItfName)) 
		return registryconfig;
	 else
		return super.lookupFc(cItf);
	}
	
	@Override
	public void bindFc (final String cItf, final Object sItf) throws NoSuchInterfaceException, IllegalBindingException, IllegalLifeCycleException {
		if (cItf.equals(allocatorContentItfName)) 
		allocatorContent = (ContentController)sItf;
	 else if (cItf.equals(clustersContentItfName)) 
		clustersContent = (ContentController)sItf;
	 else if (cItf.equals(registryItfName)) 
		registry = (NamingService)sItf;        
	 else if (cItf.equals(registryConfigItfName)) 
		registryconfig = (GenericAttributeController)sItf;  
	 else 
	   super.bindFc(cItf, sItf);
		            
	}
	
	@Override
	public void unbindFc (final String cItf) throws NoSuchInterfaceException, IllegalBindingException, IllegalLifeCycleException {
		if (cItf.equals(allocatorContentItfName)) 
		allocatorContent = null;
	 else  if (cItf.equals(clustersContentItfName)) 
		clustersContent = null;
	 else if (cItf.equals(registryItfName)) 
		registry = null;        
	 else if (cItf.equals(registryConfigItfName)) 
		registryconfig = null;    
	 else
	   super.unbindFc(cItf);
		         
	}
	
	// GenericAttributeController
	@Override
	public String getAttribute(String name) throws NoSuchAttributeException {
    if(debugNodeAttName.equals(name)){
      return Boolean.toString(debugNode);
    }else if(debugNodePortAttName.equals(name)){
      return debugNodePort;
    }else if(userAttName.equals(name)){
      return user;
    }else{
      return super.getAttribute(name);
    }
  }

	@Override
  public String[] listFcAtt() {
    return super.concat(listAtt, super.listFcAtt());
  }

  @Override
  public void setAttribute(String name, String value) throws NoSuchAttributeException {
    if(debugNodeAttName.equals(name)){
      debugNode = Boolean.parseBoolean(value);
    }else if(debugNodePortAttName.equals(name)){
      debugNodePort = value;
    }else if(userAttName.equals(name)){
      user = value;
    }else{
      super.setAttribute(name, value);
    }
  }
	// -------------------------------------------------------------------------

	/**
	 * createNode starts fractal on the physical node (via ssh) and
	 * registers the node in the registry   
	 * @param physical the hostname of the node
	 * @param virtual the virtual name of the node
	 * @return the remote Fractal component
	 * @throws InstantiationException 
	 * @throws Exception
	 */
	private Component createNode(String physical, String virtual) throws JadeException{
		final int nbTries = 100;
		
		String jadeHost = null;
    try {
      jadeHost = InetAddress.getLocalHost().getHostName();
    } catch (UnknownHostException e1) {
      e1.printStackTrace();
    }
		String jadePort = null;
    try {
      jadePort = registryconfig.getAttribute("port");
    } catch (NoSuchAttributeException e1) {
      e1.printStackTrace();
    }
		String args = virtual + "," + jadeHost + "," + jadePort;
		String separ = System.getProperty("file.separator");
		String pwd = System.getenv("PWD");
		String cp = pwd+separ+System.getProperty("java.class.path");
		cp=cp.replace(":", ":"+pwd+separ).replace(";", ";"+pwd+separ);
		cp.replace("\\","\\\\").replace(" ", "\\ ");
		String classPath = " -classpath "+cp;
		String jvmArgs = " -Dfractal.provider="+System.getProperty("fractal.provider");
		jvmArgs = jvmArgs + " -Dfraclite.config="+System.getProperty("fraclite.config");
		if(debugNode){
		  jvmArgs = jvmArgs + " -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,address="+debugNodePort+",server=y,suspend=n";
		}
		jvmArgs = jvmArgs + " -Xrunhprof:cpu=samples,depth=12,interval=100,file="+physical+"-hprof.txt";
		
		String username;
		if (user==null || user.equals("")){
		  username="";
		}else{
		  username=user+"@";
		}
		
		String java = " "+(System.getenv("JAVA_HOME")+separ+"bin"+separ+"java").replace("\\","\\\\").replace(" ", "\\ ");
		String cmd = "ssh "+username+physical+java+classPath+jvmArgs+" fr.jade.service.nodes.factory.NodeLauncher "+args;
	  cmd = cmd.replace(";", "\\;");
		log(cmd);
		ShellCommand shcmd = ShellCommand.asyncExec(cmd, null);
		shcmd.forwardAll( physical + " :: ");
		Component execNode = null;
		int notDead = nbTries;		
		while (notDead>0) {
			notDead--;
			try {
        Thread.sleep(1000);
      } catch (InterruptedException ignored) { }
			
			log("NodeFactory: Fractal not ready ...");
			execNode = registry.lookup(virtual);
			if (execNode !=null) {
				log(virtual+"("+physical+")"+" deployed ...\n");
				break;
			}
		}
		if(execNode == null) throw new JadeException("NodeFactory : no Fractal available after "+nbTries+" tries");
    try {
      Component boot = Fractal.getBootstrapComponent();
      GenericFactory gf = (GenericFactory) boot.getFcInterface("generic-factory");
      TypeFactory tf = (TypeFactory) boot.getFcInterface("type-factory");
      Component metaNode = gf.newFcInstance(tf.createFcType(new InterfaceType[]{}), "node", null);
      Reflex.getReflexController(metaNode).setLevel(ReflexController.META_LEVEL);
      Reflex.setAsDualComponent(metaNode, execNode);
    } catch (InstantiationException e) {
      throw new JadeException("NodeFactory : can't create meta node", e);
    } catch (NoSuchInterfaceException e) {
      throw new JadeException("NodeFactory : can't create meta node", e);
    }
    
    try {
      FractalUtil.getAttributeController(execNode).setAttribute("hostName", physical);
    } catch (NoSuchAttributeException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (NoSuchInterfaceException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    
		return execNode;
	}

	
	/**
	 * newNode deploies a node and instantiates the corresponding Node component
	 * (at the runtime-level and meta-level)
	 * @param physical the physical hostname of the node
	 * @param virtual the virtual name of the node (can be null, meaning equal to physical name)
	 * @return the fractal Component of the node 
	 */
	public Component newNode(String physical, String virtual) throws InstantiationException {
		Component node = null;
		if (virtual==null) virtual = physical;
		try {
			node = createNode(physical, virtual);
			
  		Fractal.getNameController(node).setFcName(virtual);
  		// register the new node in the allocator
  		allocatorContent.addFcSubComponent(node);
  		return node;
		} catch (JadeException e) {
		  log(e);
      throw new InstantiationException("Cannot create node");
    } catch (NoSuchInterfaceException e) {
      log(e);
      throw new InstantiationException("Cannot set node name. Node must have a name-controller interface");
    } catch (IllegalContentException e) {
      log(e);
      throw new InstantiationException("Cannot add node in allocator");
    } catch (IllegalLifeCycleException e) {
      log(e);
      throw new InstantiationException("Cannot add node in allocator");
    }
	}



}
